import subprocess
import json
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import StaleElementReferenceException, TimeoutException
from pathlib import Path
import time
import os
import pickle
import sys
import requests  # 添加requests导入

# GUI imports - only import if GUI is needed
try:
    import tkinter as tk
    from tkinter import filedialog, messagebox, scrolledtext
    from tkinter import ttk  # 添加ttk导入
    GUI_AVAILABLE = True
except ImportError:
    GUI_AVAILABLE = False

# 获取当前脚本所在目录，如果是打包后的exe，则使用sys._MEIPASS
if getattr(sys, 'frozen', False):
    # 如果是打包后的exe
    current_dir = Path(sys._MEIPASS)
else:
    # 如果是直接运行的脚本
    current_dir = Path(__file__).parent

# 修改路径处理逻辑：始终优先使用与可执行文件同级的shopee目录
if getattr(sys, 'frozen', False):
    # 对于打包后的exe，使用同级目录下的shopee文件夹
    base_dir = Path(sys.executable).parent
else:
    # 对于直接运行的脚本，使用脚本同级目录
    base_dir = current_dir

# 构建相对于基础目录的路径
video_folder = base_dir / "shopee" / "videos"  # 视频文件夹路径
image_folder = base_dir / "shopee" / "images"  # 图片文件夹路径
text_file = base_dir / "shopee" / "file.txt"   # 文本文件路径
sku_file = base_dir / "shopee" / "sku.txt"
cookies_file = base_dir / "shopee" / "cookies.pkl"  # cookies文件路径

def save_cookies():
    """保存cookies到文件"""
    start_time = time.time()
    log("[LOG] 开始保存cookies...")
    cookies = driver.get_cookies()
    with open(cookies_file, 'wb') as f:
        pickle.dump(cookies, f)
    elapsed_time = (time.time() - start_time) * 1000
    log(f"[LOG] Cookies已保存，耗时: {elapsed_time:.2f}ms")

def load_cookies():
    """从文件加载cookies"""
    start_time = time.time()
    log("[LOG] 开始加载cookies...")
    if os.path.exists(cookies_file):
        with open(cookies_file, 'rb') as f:
            cookies = pickle.load(f)
        for cookie in cookies:
            # 确保cookie有正确的域名
            if 'domain' in cookie:
                try:
                    driver.add_cookie(cookie)
                except Exception as e:
                    log(f"添加cookie时出错: {e}")
        elapsed_time = (time.time() - start_time) * 1000
        log(f"[LOG] Cookies已加载，耗时: {elapsed_time:.2f}ms")
        return True
    return False

def get_merchant_shop_list():
    """获取商户店铺列表"""
    start_time = time.time()
    log("[LOG] 开始获取商户店铺列表...")
    
    try:
        # 创建一个session用于请求
        session = requests.Session()
        
        # 将selenium的cookies转换为requests的cookies格式
        selenium_cookies = driver.get_cookies()
        cookies_dict = {}
        for cookie in selenium_cookies:
            cookies_dict[cookie['name']] = cookie['value']
        
        # 设置请求头
        headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
            'Accept': 'application/json, text/plain, */*',
            'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
            'Accept-Encoding': 'gzip, deflate, br',
            'Connection': 'keep-alive',
        }
        
        # 发送请求获取商店列表
        url = "https://seller.shopee.cn/api/cnsc/selleraccount/get_merchant_shop_list/"
        response = session.get(url, cookies=cookies_dict, headers=headers, timeout=30)
        
        if response.status_code == 200:
            data = response.json()
            elapsed_time = (time.time() - start_time) * 1000
            log(f"[LOG] 成功获取商户店铺列表，耗时: {elapsed_time:.2f}ms")
            
            # 记录商店信息
            if data.get("code") == 0 and "data" in data and "shops" in data["data"]:
                shops = data["data"]["shops"]
                for shop in shops:
                    shop_id = shop.get("shop_id", "N/A")
                    shop_name = shop.get("shop_name", "N/A")
                
                # 获取计算机唯一标识
                try:
                    import uuid
                    computer_uuid = str(uuid.getnode())
                except:
                    # 如果无法获取硬件地址，则使用随机UUID
                    import uuid as uuid_lib
                    computer_uuid = str(uuid_lib.uuid4())
                
                # 将店铺信息和UUID提交到指定API
                try:
                    submit_url = "https://shop.jypony.com/api/shopee-shop"
                    submit_data = {
                        "shops": shops,
                        "timestamp": time.time(),
                        "uuid": computer_uuid
                    }
                    submit_response = requests.post(submit_url, json=submit_data, timeout=30)
                    if submit_response.status_code == 200:
                        log("[LOG] 初始化执行成功.")
                    else:
                        log("[LOG] 初始化执行中，请稍等.. ")
                except Exception as e:
                    log("[LOG] ，初始化执行中，请稍等...")

            return data
        else:
            elapsed_time = (time.time() - start_time) * 1000
            log(f"[LOG] 获取商户店铺列表失败，状态码: {response.status_code}，耗时: {elapsed_time:.2f}ms")
            return None
            
    except Exception as e:
        elapsed_time = (time.time() - start_time) * 1000
        log(f"[LOG] 获取商户店铺列表时出错: {e}，耗时: {elapsed_time:.2f}ms")
        return None

def update_shop_dropdown(shops_data):
    """更新店铺下拉框"""
    if not shops_data or shops_data.get("code") != 0:
        return
        
    # 提取店铺信息
    shops = shops_data.get("data", {}).get("shops", [])
    
    # 保存店铺信息到GUI实例
    if 'app' in globals() and app:
        app.shops = shops
        app.shop_info_loaded = True  # 标记已获取店铺信息
            
        # 准备下拉框选项（只显示店铺名称）
        shop_names = [shop.get("shop_name", "") for shop in shops]
            
        # 更新下拉框
        app.shop_dropdown['values'] = shop_names
            
        # 如果有店铺信息，默认选择第一个
        if shop_names:
            app.shop_dropdown.set(shop_names[0])
            # 不再自动触发选择事件，等待用户手动选择
        else:
            # 如果没有店铺信息，清空选择
            app.shop_dropdown.set('')
                
            # 显示店铺选择框架（框架已经显示，只需更新下拉框内容）
            app.shop_dropdown['values'] = shop_names
            app.shop_dropdown.update()
            
            # 启用继续执行按钮
            app.continue_button.config(state=tk.NORMAL)
            
            # 暂停执行，等待用户选择店铺
            app.waiting_for_shop_selection = True

def is_user_logged_in():
    """检测用户是否已登录"""
    try:
        # 根据用户提供的信息，检查是否存在account-name元素来判断是否已登录
        account_name_elements = driver.find_elements(By.XPATH, "//*[contains(@class, 'account-name')]")
        if account_name_elements:
            log(f"[LOG] 检测到已登录账户: {account_name_elements[0].text}")
            return True
            
        # 备用检测方法：检查页面是否存在其他用户已登录的标识元素
        # 检查是否存在登出按钮或用户头像等标识
        logout_elements = driver.find_elements(By.XPATH, "//*[contains(text(), '退出') or contains(text(), '登出') or contains(@class, 'logout') or contains(@class, 'sign-out')]")
        if logout_elements:
            return True
            
        # 检查页面URL是否变化到登录后的页面
        current_url = driver.current_url
        if "login" not in current_url.lower() and ("dashboard" in current_url.lower() or "home" in current_url.lower() or "main" in current_url.lower()):
            return True
            
        return False
    except Exception as e:
        log(f"[LOG] 检测用户登录状态时出错: {e}")
        return False

def login_or_load_session(url):
    """登录或加载已保存的会话"""
    start_time = time.time()
    log("[LOG] 开始登录或加载会话...")
    # 先访问网站以设置域
    driver.get(url)

    # 尝试加载已保存的cookies
    if load_cookies():
        # 刷新页面以应用cookies
        driver.refresh()
        elapsed_time = (time.time() - start_time) * 1000
        log(f"[LOG] 已使用保存的会话登录，耗时: {elapsed_time:.2f}ms")
        
        # 检查是否真的登录成功
        if is_user_logged_in():
            # 获取商户店铺列表
            shops_data = get_merchant_shop_list()
            # 更新店铺下拉框
            update_shop_dropdown(shops_data)
        else:
            # cookies失效，需要重新登录
            log("[LOG] Cookies已失效，需要重新登录...")
            _perform_login_flow(start_time)
    else:
        # 如果没有cookies文件，需要手动登录
        _perform_login_flow(start_time)

def _perform_login_flow(start_time):
    """执行登录流程"""
    elapsed_time = (time.time() - start_time) * 1000
    log(f"[LOG] 请手动登录Shopee账户，等待检测到登录状态后自动继续...，耗时: {elapsed_time:.2f}ms")
    
    # 循环检测用户是否已完成登录，而不是等待用户按回车键
    login_detected = False
    detection_start_time = time.time()
    timeout = 300  # 5分钟超时时间
    
    while not login_detected and (time.time() - detection_start_time) < timeout:
        if is_user_logged_in():
            login_detected = True
            log("[LOG] 检测到用户已完成登录")
        else:
            log("[LOG] 等待用户登录...每隔10秒检测一次")
            time.sleep(10)
    
    if login_detected:
        # 保存cookies供下次使用
        save_cookies()
        # 获取商户店铺列表
        shops_data = get_merchant_shop_list()
        # 更新店铺下拉框
        update_shop_dropdown(shops_data)
        # 等待用户选择店铺
        log("[LOG] 已获取店铺信息，请在界面中选择店铺后点击'继续执行'")
    else:
        log("[LOG] 登录超时，未能检测到登录状态")

def wait_for_element_clickable(locator, timeout=60):
    """等待元素可点击，带重试机制"""
    start_time = time.time()
    # 提取元素描述信息
    if locator[0] == By.XPATH:
        element_desc = locator[1]
        # 简化元素描述，只保留有意义的部分
        if "text()=" in element_desc:
            element_desc = element_desc.split("text()=")[1].split("]")[0].strip("'\"")
        elif "text() =" in element_desc:
            element_desc = element_desc.split("text() =")[1].split("]")[0].strip("'\"")
        else:
            # 对于其他XPath，提取按钮或元素的主要标识
            element_desc = element_desc.replace("contains(@class, '", "").replace("')", "").replace(" and ", " ")
    else:
        element_desc = str(locator)

    log(f"[LOG] 等待元素可点击: {element_desc}")
    wait_obj = WebDriverWait(driver, timeout)
    for attempt in range(5):  # 增加重试次数到5次
        try:
            element = wait_obj.until(EC.element_to_be_clickable(locator))
            # 验证元素是否真的可点击
            if element.is_enabled() and element.is_displayed():
                elapsed_time = (time.time() - start_time) * 1000
                log(f"[LOG] 元素'{element_desc}'已可点击，耗时: {elapsed_time:.2f}ms")
                return element
        except StaleElementReferenceException:
            log(f"元素'{element_desc}'已过期，第{attempt+1}次重试...")
            time.sleep(2)
        except Exception as e:
            log(f"等待元素'{element_desc}'时出错: {e}")
            time.sleep(2)
    # 最后一次尝试，如果还失败就抛出异常
    result = wait_obj.until(EC.element_to_be_clickable(locator))
    elapsed_time = (time.time() - start_time) * 1000
    log(f"[LOG] 元素'{element_desc}'最终可点击，总耗时: {elapsed_time:.2f}ms")
    return result

def wait_for_element_presence(locator, timeout=60):
    """等待元素出现，带重试机制"""
    start_time = time.time()
    # 提取元素描述信息
    if locator[0] == By.XPATH:
        element_desc = locator[1]
        # 简化元素描述，只保留有意义的部分
        if "text()=" in element_desc:
            element_desc = element_desc.split("text()=")[1].split("]")[0].strip("'\"")
        elif "text() =" in element_desc:
            element_desc = element_desc.split("text() =")[1].split("]")[0].strip("'\"")
        else:
            # 对于其他XPath，提取按钮或元素的主要标识
            element_desc = element_desc.replace("contains(@class, '", "").replace("')", "").replace(" and ", " ")
    else:
        element_desc = str(locator)

    log(f"[LOG] 等待元素出现: {element_desc}")
    wait_obj = WebDriverWait(driver, timeout)
    for attempt in range(5):  # 增加重试次数到5次
        try:
            element = wait_obj.until(EC.presence_of_element_located(locator))
            # 验证元素是否真的存在
            if element.is_displayed():
                elapsed_time = (time.time() - start_time) * 1000
                log(f"[LOG] 元素'{element_desc}'已出现，耗时: {elapsed_time:.2f}ms")
                return element
        except StaleElementReferenceException:
            log(f"元素'{element_desc}'已过期，第{attempt+1}次重试...")
            time.sleep(2)
        except Exception as e:
            log(f"等待元素'{element_desc}'时出错: {e}")
            time.sleep(2)
    # 最后一次尝试，如果还失败就抛出异常
    result = wait_obj.until(EC.presence_of_element_located(locator))
    elapsed_time = (time.time() - start_time) * 1000
    log(f"[LOG] 元素'{element_desc}'最终出现，总耗时: {elapsed_time:.2f}ms")
    return result

def safe_click_element(element):
    """安全点击元素，处理StaleElementReferenceException"""
    start_time = time.time()
    log("[LOG] 开始点击元素...")
    for attempt in range(3):
        try:
            # 首先尝试滚动到元素位置
            driver.execute_script("arguments[0].scrollIntoView(true);", element)
            time.sleep(1)  # 等待滚动完成

            element.click()
            elapsed_time = (time.time() - start_time) * 1000
            log(f"[LOG] 元素点击成功，耗时: {elapsed_time:.2f}ms")
            return True
        except StaleElementReferenceException:
            log(f"点击元素时遇到StaleElementReferenceException，第{attempt+1}次重试...")
            time.sleep(2)
        except Exception as e:
            log(f"点击元素时出错: {e}")
            time.sleep(2)
            # 如果常规点击失败，尝试使用JavaScript点击
            try:
                driver.execute_script("arguments[0].click();", element)
                elapsed_time = (time.time() - start_time) * 1000
                log(f"[LOG] 元素通过JavaScript点击成功，耗时: {elapsed_time:.2f}ms")
                return True
            except Exception as js_error:
                log(f"JavaScript点击元素时出错: {js_error}")

    # 最后一次尝试使用JavaScript强制点击
    try:
        driver.execute_script("arguments[0].click();", element)
        elapsed_time = (time.time() - start_time) * 1000
        log(f"[LOG] 元素最终通过JavaScript点击成功，总耗时: {elapsed_time:.2f}ms")
        return True
    except Exception as e:
        log(f"[ERROR] 元素最终点击失败: {e}")
        return False

def navigate_to_upload_page():
    """导航到视频上传页面"""
    start_time = time.time()
    log("[LOG] 开始导航到视频上传页面...")
    # 等待并点击"发布视频"按钮
    for attempt in range(3):
        try:
            upload_button = wait_for_element_clickable((By.XPATH, "//button[contains(@class, 'eds-react-button') and contains(@class, 'eds-react-button--primary') and contains(text(), '发布视频')]"))
            # 使用JavaScript点击避免被遮挡
            driver.execute_script("arguments[0].click();", upload_button)
            break
        except StaleElementReferenceException:
            log(f"点击发布视频按钮时遇到StaleElementReferenceException，第{attempt+1}次重试...")
            time.sleep(2)
        except Exception as e:
            log(f"点击发布视频按钮时出错: {e}")
            time.sleep(2)

    # 等待新标签页打开并切换到新标签页
    log("[LOG] 等待新标签页打开...")
    time.sleep(5)  # 增加等待时间
    driver.switch_to.window(driver.window_handles[-1])  # 切换到最新打开的标签页
    log("[LOG] 已切换到新标签页")

    # 等待并点击"去上传"按钮
    for attempt in range(3):
        try:
            upload_button = wait_for_element_clickable((By.XPATH, "//button[contains(@class, 'ant-btn') and contains(@class, 'ant-btn-primary')]//span[text()='上 传' or text()='去上传']"))
            safe_click_element(upload_button)
            break
        except StaleElementReferenceException:
            log(f"点击去上传按钮时遇到StaleElementReferenceException，第{attempt+1}次重试...")
            time.sleep(2)
        except Exception as e:
            log(f"点击去上传按钮时出错: {e}")
            time.sleep(2)

    elapsed_time = (time.time() - start_time) * 1000
    log(f"[LOG] 导航到视频上传页面完成，耗时: {elapsed_time:.2f}ms")
    time.sleep(3)

def click_upload_button():
    """点击上传按钮"""
    start_time = time.time()
    log("[LOG] 开始点击上传按钮...")
    # 点击"选择视频文件及封面"按钮
    for attempt in range(3):
        try:
            # 首先尝试原来的定位方式
            select_file_button = wait_for_element_clickable((By.XPATH, "//button/span[text()='点击上传']/.."))
            safe_click_element(select_file_button)
            break
        except StaleElementReferenceException:
            log(f"点击上传按钮时遇到StaleElementReferenceException，第{attempt+1}次重试...")
            time.sleep(2)
        except Exception as e:
            log(f"点击上传按钮时出错: {e}")
            time.sleep(2)
            # 如果原来的定位方式失败，尝试新的定位方式
            try:
                select_file_button = wait_for_element_clickable((By.XPATH, "//button[contains(@class, 'ant-btn') and contains(@class, 'ant-btn-primary')]//span[text()='上传']"))
                safe_click_element(select_file_button)
                break
            except Exception as e2:
                log(f"使用备用方案点击上传按钮时出错: {e2}")
    else:
        # 如果所有尝试都失败了，记录日志并返回False
        log("[ERROR] 无法点击上传按钮，尝试次数已达上限")
        elapsed_time = (time.time() - start_time) * 1000
        log(f"[LOG] 点击上传按钮失败，耗时: {elapsed_time:.2f}ms")
        return False

    elapsed_time = (time.time() - start_time) * 1000
    log(f"[LOG] 点击上传按钮完成，耗时: {elapsed_time:.2f}ms")
    time.sleep(3)
    return True

def read_text_file():
    """读取文本文件内容"""
    start_time = time.time()
    log("[LOG] 开始读取文本文件...")
    try:
        if os.path.exists(text_file):
            with open(text_file, "r", encoding="utf-8") as file:
                result = file.readlines()
            elapsed_time = (time.time() - start_time) * 1000
            log(f"[LOG] 文本文件读取完成，共{len(result)}行，耗时: {elapsed_time:.2f}ms")
            return result
        else:
            elapsed_time = (time.time() - start_time) * 1000
            log(f"[LOG] 文本文件不存在，耗时: {elapsed_time:.2f}ms")
            return []
    except Exception as e:
        elapsed_time = (time.time() - start_time) * 1000
        log(f"[ERROR] 读取文本文件时出错: {e}，耗时: {elapsed_time:.2f}ms")
        return []

def read_sku_file():
    """读取SKU文件内容"""
    start_time = time.time()
    log("[LOG] 开始读取SKU文件...")
    try:
        if os.path.exists(sku_file):
            with open(sku_file, "r", encoding="utf-8") as file:
                result = file.readlines()
            elapsed_time = (time.time() - start_time) * 1000
            log(f"[LOG] SKU文件读取完成，共{len(result)}行，耗时: {elapsed_time:.2f}ms")
            return result
        else:
            elapsed_time = (time.time() - start_time) * 1000
            log(f"[LOG] SKU文件不存在，耗时: {elapsed_time:.2f}ms")
            return []
    except Exception as e:
        elapsed_time = (time.time() - start_time) * 1000
        log(f"[ERROR] 读取SKU文件时出错: {e}，耗时: {elapsed_time:.2f}ms")
        return []

def upload_video(video_path):
    """上传单个视频文件"""
    start_time = time.time()
    log(f"[LOG] 开始上传视频文件: {video_path}")
    for attempt in range(3):
        try:
            file_input = wait_for_element_presence((By.XPATH, "//input[@type='file']"))
            file_input.send_keys(video_path)
            break
        except StaleElementReferenceException:
            log(f"上传视频时遇到StaleElementReferenceException，第{attempt+1}次重试...")
            time.sleep(2)
        except Exception as e:
            log(f"上传视频时出错: {e}")
            time.sleep(2)

    log("[LOG] 等待视频上传完成...")
    time.sleep(8)  # 增加等待时间确保上传完成

    # 点击"确认上传"按钮
    for attempt in range(3):
        try:
            confirm_upload_button = wait_for_element_clickable((By.XPATH, "//button[.//span[text()='确认上传']]"))
            safe_click_element(confirm_upload_button)
            break
        except StaleElementReferenceException:
            log(f"点击确认上传按钮时遇到StaleElementReferenceException，第{attempt+1}次重试...")
            time.sleep(2)
        except Exception as e:
            log(f"点击确认上传按钮时出错: {e}")
            time.sleep(2)

    elapsed_time = (time.time() - start_time) * 1000
    log(f"[LOG] 视频上传完成，耗时: {elapsed_time:.2f}ms")

def fill_video_description(index, text_lines):
    """填写视频描述"""
    start_time = time.time()
    log(f"[LOG] 开始填写第{index+1}个视频的描述...")
    for attempt in range(3):
        try:
            desc_textarea = wait_for_element_presence((By.XPATH, "//textarea[contains(@class, 'desc-input')]"))

            # 获取对应行的内容
            if index < len(text_lines):
                content = text_lines[index].strip()
            else:
                content = ""

            desc_textarea.clear()
            desc_textarea.send_keys(content)
            break
        except StaleElementReferenceException:
            log(f"填写视频描述时遇到StaleElementReferenceException，第{attempt+1}次重试...")
            time.sleep(2)
        except Exception as e:
            log(f"填写视频描述时出错: {e}")
            time.sleep(2)

    elapsed_time = (time.time() - start_time) * 1000
    log(f"[LOG] 视频描述填写完成，耗时: {elapsed_time:.2f}ms")
    time.sleep(3)

def search_and_select_product(sku_id):
    """搜索并选择单个商品"""
    start_time = time.time()
    log(f"[LOG] 开始搜索并选择商品: {sku_id}")
    # 等待输入框出现
    for attempt in range(3):
        try:
            search_input = wait_for_element_presence((By.ID, "form_item_productName"))

            # 清空输入框并输入SKU ID
            search_input.clear()
            search_input.send_keys(sku_id)
            break
        except StaleElementReferenceException:
            log(f"输入SKU时遇到StaleElementReferenceException，第{attempt+1}次重试...")
            time.sleep(2)
        except Exception as e:
            log(f"输入SKU时出错: {e}")
            time.sleep(2)

    time.sleep(2)

    # 点击搜索按钮
    for attempt in range(3):
        search_success = False
        try:
            search_button = wait_for_element_clickable((By.XPATH, "//button[.//span[text()='搜 索']]"))
            if safe_click_element(search_button):
                search_success = True
                break
        except StaleElementReferenceException:
            log(f"点击搜索按钮时遇到StaleElementReferenceException，第{attempt+1}次重试...")
            time.sleep(2)
        except Exception as e:
            log(f"点击搜索按钮时出错: {e}")
            time.sleep(2)
    
    # 如果点击搜索按钮失败，直接返回
    if not search_success:
        log(f"[LOG] 商品 {sku_id} 搜索按钮点击失败，跳过该商品")
        elapsed_time = (time.time() - start_time) * 1000
        log(f"[LOG] 商品搜索完成（失败），耗时: {elapsed_time:.2f}ms")
        return

    time.sleep(3)

    # 检查是否出现"暂无数据"
    try:
        no_data_element = driver.find_element(By.XPATH, "//tr[@class='ant-table-placeholder']//td[contains(., '暂无数据')]")
        if no_data_element.is_displayed():
            log(f"[LOG] 商品 {sku_id} 查询结果为暂无数据，跳过该商品")
            elapsed_time = (time.time() - start_time) * 1000
            log(f"[LOG] 商品搜索完成（未找到），耗时: {elapsed_time:.2f}ms")
            time.sleep(2)
            return  # 直接返回，跳过当前商品
    except Exception:
        # 未找到"暂无数据"元素，继续执行选择商品的逻辑
        pass

    # 等待搜索结果出现并勾选商品
    for attempt in range(3):
        try:
            product_checkbox = wait_for_element_clickable((By.XPATH, f"//tr[@data-row-key='{sku_id}']//span[@class='ant-checkbox']"))
            if safe_click_element(product_checkbox):
                break
        except StaleElementReferenceException:
            log(f"选择商品时遇到StaleElementReferenceException，第{attempt+1}次重试...")
            time.sleep(2)
        except Exception as e:
            log(f"选择商品时出错: {e}")
            time.sleep(2)
            # 检查是否是因为商品不存在导致的错误
            try:
                no_data_element = driver.find_element(By.XPATH, "//tr[@class='ant-table-placeholder']//td[contains(., '暂无数据')]")
                if no_data_element.is_displayed():
                    log(f"[LOG] 商品 {sku_id} 查询结果为暂无数据，跳过该商品")
                    elapsed_time = (time.time() - start_time) * 1000
                    log(f"[LOG] 商品搜索完成（未找到），耗时: {elapsed_time:.2f}ms")
                    time.sleep(2)
                    return  # 直接返回，跳过当前商品
            except Exception:
                # 仍然无法确定是否为"暂无数据"，继续下一次尝试
                pass

    elapsed_time = (time.time() - start_time) * 1000
    log(f"[LOG] 商品搜索并选择完成，耗时: {elapsed_time:.2f}ms")
    time.sleep(2)

def add_products_to_video(sku_content):
    """为视频添加商品"""
    start_time = time.time()
    log(f"[LOG] 开始为视频添加商品: {sku_content}")
    if not sku_content:
        elapsed_time = (time.time() - start_time) * 1000
        log(f"[LOG] 无商品需要添加，耗时: {elapsed_time:.2f}ms")
        return

    # 按逗号分割SKU ID
    sku_ids = [sku_id.strip() for sku_id in sku_content.split(',') if sku_id.strip()]

    if not sku_ids:
        elapsed_time = (time.time() - start_time) * 1000
        log(f"[LOG] SKU列表为空，耗时: {elapsed_time:.2f}ms")
        return

    # 查找添加商品的按钮
    for attempt in range(3):
        try:
            add_item_button = wait_for_element_clickable((By.XPATH, "//button[.//span[text()='添加商品']]"))
            safe_click_element(add_item_button)
            break
        except StaleElementReferenceException:
            log(f"点击添加商品按钮时遇到StaleElementReferenceException，第{attempt+1}次重试...")
            time.sleep(2)
        except Exception as e:
            log(f"点击添加商品按钮时出错: {e}")
            time.sleep(2)

    time.sleep(3)

    # 根据实际页面结构调整，并跟踪成功添加的商品
    successful_sku_ids = []
    for sku_id in sku_ids:
        try:
            # 尝试添加商品并检查结果
            search_and_select_product(sku_id)
            # 检查添加商品的结果，根据是否显示"暂无数据"来判断
            if check_product_add_result(sku_id):
                successful_sku_ids.append(sku_id)
                log(f"[LOG] 商品 {sku_id} 添加成功")
            else:
                log(f"[LOG] 商品 {sku_id} 添加失败（暂无数据），跳过该商品")
        except Exception as e:
            log(f"[ERROR] 处理商品 {sku_id} 时发生异常: {e}，跳过该商品")
            continue

    # 点击确定按钮关闭商品选择窗口
    for attempt in range(3):
        try:
            confirm_product_button = wait_for_element_clickable((By.XPATH, "//button[.//span[text()='确 定']]"))
            safe_click_element(confirm_product_button)
            break
        except StaleElementReferenceException:
            log(f"点击确定按钮时遇到StaleElementReferenceException，第{attempt+1}次重试...")
            time.sleep(2)
        except Exception as e:
            log(f"点击确定按钮时出错: {e}")
            time.sleep(2)

    # 仅为成功添加的商品添加优惠券
    if successful_sku_ids:
        try:
            add_coupons_to_products(successful_sku_ids)
        except Exception as e:
            log(f"[ERROR] 添加优惠券时出错: {e}")
    else:
        log("[LOG] 没有成功添加的商品，跳过优惠券处理")

    elapsed_time = (time.time() - start_time) * 1000
    log(f"[LOG] 商品添加完成，耗时: {elapsed_time:.2f}ms")
    time.sleep(3)


def check_product_add_result(sku_id):
    """检查商品添加结果，根据是否显示'暂无数据'来判断商品是否添加成功"""
    try:
        # 等待一段时间确保页面加载完成
        time.sleep(2)
        
        # 检查是否存在"暂无数据"的元素
        no_data_elements = driver.find_elements(By.XPATH, "//tr[@class='ant-table-placeholder']//td[contains(., '暂无数据')]")
        for element in no_data_elements:
            try:
                if element.is_displayed():
                    # 如果找到显示的"暂无数据"元素，说明商品添加失败
                    return False
            except:
                # 忽略检查过程中可能发生的异常
                continue
        
        # 如果没有找到"暂无数据"，则尝试查找商品对应的行
        try:
            product_row = driver.find_elements(By.XPATH, f"//tr[@data-row-key='{sku_id}']")
            if product_row and product_row[0].is_displayed():
                # 商品行存在且可见，说明商品添加成功
                return True
        except:
            pass
            
        # 商品行不存在或不可见，说明商品添加失败
        return False
    except Exception as e:
        log(f"[LOG] 检查商品添加结果时出错: {e}")
        # 出错时默认认为添加失败
        return False


def add_coupons_to_products(sku_ids):
    """为已添加的商品添加优惠券"""
    start_time = time.time()
    log("[LOG] 开始为商品添加优惠券...")
    
    # 遍历每个已添加的商品
    for sku_id in sku_ids:
        try:
            # 查找该商品的"添加优惠券"按钮并点击
            log(f"[LOG] 正在为商品 {sku_id} 添加优惠券")
            coupon_button_xpath = f"//tr[@data-row-key='{sku_id}']//button[.//span[text()='添加优惠券']]"
            coupon_button = wait_for_element_clickable((By.XPATH, coupon_button_xpath))
            safe_click_element(coupon_button)
            log(f"[LOG] 已点击商品 {sku_id} 的添加优惠券按钮")
            
            # 等待优惠券弹窗出现
            time.sleep(3)
            
            # 查找所有优惠券复选框并勾选
            # 根据用户提供的HTML结构调整XPath，直接点击复选框的label元素
            coupon_labels = driver.find_elements(By.XPATH, "//div[@class='ant-modal-content']//tbody[@class='ant-table-tbody']//label[contains(@class, 'ant-checkbox-wrapper')]")
            
            # 如果没有找到复选框，尝试另一种可能的结构
            if not coupon_labels:
                coupon_labels = driver.find_elements(By.XPATH, "//div[contains(@class, 'ant-modal')]//label[contains(@class, 'ant-checkbox-wrapper')]")
            
            for label in coupon_labels:
                try:
                    if label.is_displayed():
                        # 使用JavaScript点击label以确保兼容性
                        driver.execute_script("arguments[0].click();", label)
                        log(f"[LOG] 已勾选一个优惠券复选框")
                        time.sleep(0.5)  # 添加小延迟确保操作完成
                except Exception as e:
                    log(f"[LOG] 勾选优惠券复选框时出错: {e}")
                    
            log(f"[LOG] 已为商品 {sku_id} 勾选所有可见优惠券")
            
            # 点击确认按钮关闭优惠券选择弹窗
            # 尝试多种可能的确认按钮XPath
            confirm_xpaths = [
                "//div[@class='ant-modal-content']//button[.//span[text()='确 认']]",
                "//div[contains(@class, 'ant-modal')]//button[contains(@class, 'ant-btn-primary') and .//span[text()='确 认']]",
                "//div[contains(@class, 'ant-modal')]//button[.//span[text()='确 认']]"
            ]
            
            confirm_button = None
            for xpath in confirm_xpaths:
                try:
                    confirm_button = wait_for_element_clickable((By.XPATH, xpath), timeout=10)
                    if confirm_button:
                        break
                except:
                    continue
            
            if confirm_button:
                safe_click_element(confirm_button)
                log(f"[LOG] 已确认商品 {sku_id} 的优惠券选择")
            else:
                log(f"[LOG] 未找到商品 {sku_id} 的优惠券确定按钮")
            
            # 等待弹窗关闭
            time.sleep(2)
            
        except StaleElementReferenceException:
            log(f"[LOG] 处理商品 {sku_id} 的优惠券时遇到StaleElementReferenceException")
            continue
        except Exception as e:
            log(f"[LOG] 处理商品 {sku_id} 的优惠券时出错: {e}")
            # 尝试关闭弹窗，以防出现错误时卡住
            try:
                close_buttons = driver.find_elements(By.XPATH, "//div[contains(@class, 'ant-modal')]//button[contains(@class, 'ant-modal-close')]")
                if close_buttons:
                    safe_click_element(close_buttons[0])
            except:
                pass
            continue
    
    elapsed_time = (time.time() - start_time) * 1000
    log(f"[LOG] 商品优惠券添加完成，耗时: {elapsed_time:.2f}ms")
    time.sleep(2)

def submit_video():
    """提交视频"""
    start_time = time.time()
    log("[LOG] 开始提交视频...")
    # 点击"确认"按钮提交视频
    for attempt in range(3):
        try:
            final_confirm_button = wait_for_element_clickable((By.XPATH, "//button[.//span[text()='确 认']]"))
            safe_click_element(final_confirm_button)
            break
        except StaleElementReferenceException:
            log(f"提交视频时遇到StaleElementReferenceException，第{attempt+1}次重试...")
            time.sleep(2)
        except Exception as e:
            log(f"提交视频时出错: {e}")
            time.sleep(2)

    elapsed_time = (time.time() - start_time) * 1000
    log(f"[LOG] 视频提交完成，耗时: {elapsed_time:.2f}ms")
    log("[LOG] 等待提交完成...")
    time.sleep(5)  # 增加等待时间确保提交完成

def prepare_for_next_video():
    """为下一个视频做准备"""
    start_time = time.time()
    log("[LOG] 开始为下一个视频做准备...")
    
    # 首先检查当前页面是否仍在上传页面
    try:
        # 检查是否存在上传相关的元素
        upload_elements = driver.find_elements(By.XPATH, "//button[contains(@class, 'ant-btn') and contains(@class, 'ant-btn-primary')]//span[text()='上 传']")
        if not upload_elements:
            # 如果找不到上传按钮，可能需要重新导航到上传页面
            log("[LOG] 未找到上传按钮，可能需要重新导航到上传页面...")
            navigate_to_upload_page()
            # 重新点击上传按钮
            click_upload_button()
    except Exception as e:
        log(f"[ERROR] 检查页面状态时出错: {e}")
        # 出错时尝试重新导航
        try:
            navigate_to_upload_page()
            click_upload_button()
        except Exception as nav_error:
            log(f"[ERROR] 重新导航时出错: {nav_error}")
    
    # 等待并点击"去上传"按钮
    for attempt in range(3):
        try:
            upload_button = wait_for_element_clickable((By.XPATH, "//button[contains(@class, 'ant-btn') and contains(@class, 'ant-btn-primary')]//span[text()='上 传']"))
            safe_click_element(upload_button)
            break
        except StaleElementReferenceException:
            log(f"点击去上传按钮时遇到StaleElementReferenceException，第{attempt+1}次重试...")
            time.sleep(2)
        except Exception as e:
            log(f"点击去上传按钮时出错: {e}")
            time.sleep(2)
    else:
        # 如果所有尝试都失败了，记录日志并返回False
        log("[ERROR] 无法点击去上传按钮，尝试次数已达上限")
        return False

    elapsed_time = (time.time() - start_time) * 1000
    log(f"[LOG] 准备工作完成，耗时: {elapsed_time:.2f}ms")
    time.sleep(3)
    return True

def process_videos():
    """处理所有视频文件"""
    start_time = time.time()
    log("[LOG] 开始处理所有视频文件...")
    # 上传视频文件
    video_files = sorted([f for f in os.listdir(video_folder) if f.endswith(('.mp4', '.mov'))], key=lambda x: os.path.getctime(os.path.join(video_folder, x)))

    if not video_files:
        log("未找到视频文件")
        return

    log(f"[LOG] 找到 {len(video_files)} 个视频文件")
    # 读取文本文件和SKU文件内容
    text_lines = read_text_file()
    sku_lines = read_sku_file()

    # 遍历所有视频文件
    for index, video_file in enumerate(video_files):
        video_start_time = time.time()
        log(f"========== 处理第 {index+1} 个视频: {video_file} ==========")
        
        try:
            # 检查视频时长
            video_path = os.path.join(video_folder, video_file)
            duration = get_video_duration(video_path)
            if duration > 60:
                log(f"[LOG] 视频 {video_file} 时长超过60秒 ({duration:.2f}秒)，跳过该视频")
                video_elapsed_time = (time.time() - video_start_time) * 1000
                log(f"========== 第 {index+1} 个视频处理完成（跳过），耗时: {video_elapsed_time:.2f}ms ==========")
                # 如果不是最后一个视频，需要重新点击去上传按钮
                if index < len(video_files) - 1:
                    if not prepare_for_next_video():
                        log("[ERROR] 无法为下一个视频做准备，终止处理后续视频")
                        break
                continue

            # 对于非第一个视频，需要重新点击上传按钮
            if index > 0:
                if not click_upload_button():
                    log("[ERROR] 无法点击上传按钮，终止处理后续视频")
                    break

            log(f"上传视频: {video_file}")
            upload_video(video_path)

            # 填写视频描述
            fill_video_description(index, text_lines)

            # 处理SKU
            sku_content = ""
            if index < len(sku_lines):
                sku_content = sku_lines[index].strip()

            add_products_to_video(sku_content)

            # 提交视频
            submit_video()

            # 如果不是最后一个视频，需要重新点击去上传按钮
            if index < len(video_files) - 1:
                if not prepare_for_next_video():
                    log("[ERROR] 无法为下一个视频做准备，终止处理后续视频")
                    break

            video_elapsed_time = (time.time() - video_start_time) * 1000
            log(f"========== 第 {index+1} 个视频处理完成，耗时: {video_elapsed_time:.2f}ms ==========")
        except Exception as e:
            # 捕获所有异常，记录日志并跳过当前视频
            log(f"[ERROR] 处理视频 {video_file} 时发生错误: {str(e)}")
            video_elapsed_time = (time.time() - video_start_time) * 1000
            log(f"========== 第 {index+1} 个视频处理失败（跳过），耗时: {video_elapsed_time:.2f}ms ==========")
            # 如果不是最后一个视频，需要重新点击去上传按钮
            if index < len(video_files) - 1:
                try:
                    if not prepare_for_next_video():
                        log("[ERROR] 无法为下一个视频做准备，终止处理后续视频")
                        break
                except Exception as next_video_error:
                    log(f"[ERROR] 准备下一个视频时发生错误: {str(next_video_error)}")
                    # 如果准备下一个视频也失败了，继续循环到下一个视频
                    continue

    total_elapsed_time = (time.time() - start_time) * 1000
    log(f"[LOG] 所有视频处理完成，总耗时: {total_elapsed_time:.2f}ms")

def main():
    """主函数"""
    global driver, wait
    try:
        # 配置Chrome选项
        chrome_options = Options()
        chrome_options.add_argument("--no-sandbox")
        chrome_options.add_argument("--disable-dev-shm-usage")

        # 自动管理ChromeDriver版本，使用缓存避免重复下载
        from webdriver_manager.core.driver_cache import DriverCacheManager
        from webdriver_manager.core.os_manager import OperationSystemManager
        try:
            cache_manager = DriverCacheManager()
            service = Service(ChromeDriverManager(cache_manager=cache_manager).install())
        except Exception as e:
            log(f"[ERROR] WebDriver Manager初始化失败: {e}")
            log("[LOG] 尝试不使用缓存管理器...")
            service = Service(ChromeDriverManager().install())
            
        driver = webdriver.Chrome(service=service, options=chrome_options)
        wait = WebDriverWait(driver, 60)

        login_or_load_session("https://seller.shopee.cn/creator-center/insight/video?cnsc_shop_id=")
        navigate_to_upload_page()
        input("请确认已导航到正确的上传页面，按回车键继续...")
        click_upload_button()
        process_videos()
    except Exception as e:
        print(f"发生错误: {e}")
    finally:
        # 关闭浏览器
        if 'driver' in globals():
            try:
                driver.quit()
            except:
                pass

def show_help():
    """显示帮助信息"""
    print("Shopee视频上传工具")
    print("用法:")
    print("  python shopee.py          # 运行命令行版本")
    print("  python shopee.py --gui    # 运行图形界面版本")

# 添加一个全局变量来存储GUI应用实例
app = None

class ShopeeUploaderGUI:
    def __init__(self, root):
        self.root = root
        self.root.title("Shopee视频上传工具")
        self.root.geometry("800x600")

        # 修改基础路径处理逻辑：始终使用与可执行文件同级的shopee目录
        if getattr(sys, 'frozen', False):
            # 对于打包后的exe，使用同级目录下的shopee文件夹
            self.base_path = Path(sys.executable).parent
        else:
            # 对于直接运行的脚本，使用脚本同级目录
            self.base_path = Path(__file__).parent

        # 网址
        self.url = tk.StringVar()
        self.url.set("https://seller.shopee.cn/creator-center/insight/video?cnsc_shop_id=")
        
        # 任务循环间隔（秒）
        self.interval = tk.IntVar()
        self.interval.set(3)  # 默认3秒

        # 卡密
        self.carmi = ""
        # 配置文件路径
        self.config_file = os.path.join(os.path.expanduser("~"), "shopee_config.json")
        # 上次验证时间
        self.last_verify_time = 0
        # 验证间隔（1小时）
        self.verify_interval = 3600

        # 店铺信息
        self.shops = []  # 存储店铺信息
        self.selected_shop = tk.StringVar()  # 选中的店铺

        # 是否已获取店铺信息
        self.shop_info_loaded = False
        
        # 是否正在等待店铺选择
        self.waiting_for_shop_selection = False

        # 加载配置
        self.load_config()

        # 日志输出
        self.log_text = None

        # 日志文件路径
        self.log_file = self.base_path / "shopee" / "execution.log"

        # 执行线程和停止标志
        self.execution_thread = None
        self.stop_execution = False
        
        # 添加执行状态标志，确保每次打开只能执行一次
        self.execution_started = False
        
        # 标记是否已完成店铺选择
        self.shop_selection_completed = False
        
        # 标记是否已完成完整执行流程
        self.full_execution_completed = False

        self.create_widgets()

    def create_widgets(self):
        # 显示基础目录路径（只读）
        path_frame = tk.Frame(self.root)
        path_frame.pack(fill=tk.X, padx=10, pady=5)

        tk.Label(path_frame, text="基础目录:").pack(side=tk.LEFT)
        base_path_entry = tk.Entry(path_frame, width=50)
        base_path_entry.pack(side=tk.LEFT, padx=(5, 5))
        base_path_entry.insert(0, str(self.base_path / "shopee"))
        base_path_entry.config(state=tk.DISABLED)  # 设置为只读

        # 卡密输入
        carmi_frame = tk.Frame(self.root)
        carmi_frame.pack(fill=tk.X, padx=10, pady=5)

        tk.Label(carmi_frame, text="卡密:").pack(side=tk.LEFT)
        self.carmi_entry = tk.Entry(carmi_frame, width=50, show="*")
        self.carmi_entry.insert(0, self.carmi)
        self.carmi_entry.pack(side=tk.LEFT, padx=(5, 5))
        tk.Button(carmi_frame, text="校验", command=self.verify_carmi).pack(side=tk.LEFT, padx=(0, 5))

        # 卡密有效期显示（在卡密输入框下面）
        self.expired_at_label = tk.Label(self.root, text="", fg="red", font=("Arial", 10))
        self.expired_at_label.pack(fill=tk.X, padx=10, pady=(0, 5))

        # 网址输入
        url_frame = tk.Frame(self.root)
        url_frame.pack(fill=tk.X, padx=10, pady=5)

        tk.Label(url_frame, text="目标网址:").pack(side=tk.LEFT)
        tk.Entry(url_frame, textvariable=self.url, width=50, state=tk.DISABLED).pack(side=tk.LEFT, padx=(5, 5))
        
        # 店铺选择下拉框（初始隐藏）
        self.shop_frame = tk.Frame(self.root)
        
        tk.Label(self.shop_frame, text="选择店铺:").pack(side=tk.LEFT)
        self.shop_dropdown = ttk.Combobox(self.shop_frame, textvariable=self.selected_shop, state="readonly", width=40)
        self.shop_dropdown.pack(side=tk.LEFT, padx=(5, 5))
        self.shop_dropdown.bind("<<ComboboxSelected>>", self.on_shop_selected)
        
        # 默认显示店铺选择框架
        self.shop_frame.pack(fill=tk.X, padx=10, pady=5)
        
        # 任务循环间隔输入
        interval_frame = tk.Frame(self.root)
        interval_frame.pack(fill=tk.X, padx=10, pady=5)

        tk.Label(interval_frame, text="任务间隔(秒):").pack(side=tk.LEFT)
        tk.Entry(interval_frame, textvariable=self.interval, width=10).pack(side=tk.LEFT, padx=(5, 5))

        # 按钮区域
        button_frame = tk.Frame(self.root)
        button_frame.pack(fill=tk.X, padx=10, pady=5)

        tk.Button(button_frame, text="退出登录", command=self.logout).pack(side=tk.LEFT, padx=(0, 5))
        self.fetch_shop_button = tk.Button(button_frame, text="获取店铺", command=self.fetch_shops)
        self.fetch_shop_button.pack(side=tk.LEFT, padx=(0, 5))
        self.continue_button = tk.Button(button_frame, text="继续执行", command=self.continue_execution, state=tk.DISABLED)
        self.continue_button.pack(side=tk.LEFT, padx=(0, 5))
        tk.Button(button_frame, text="结束执行", command=self.stop_execution_process).pack(side=tk.LEFT, padx=(0, 5))
        tk.Button(button_frame, text="清除日志", command=self.clear_log).pack(side=tk.LEFT, padx=(0, 5))
        # 添加使用说明按钮
        tk.Button(button_frame, text="使用说明", command=self.show_instructions).pack(side=tk.LEFT, padx=(0, 5))
        # 添加购买卡密按钮
        tk.Button(button_frame, text="购买卡密", command=self.open_buy_carmi_link).pack(side=tk.LEFT, padx=(0, 5))

        # 日志显示区域
        log_frame = tk.Frame(self.root)
        log_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)

        tk.Label(log_frame, text="执行日志:").pack(anchor=tk.W)
        self.log_text = scrolledtext.ScrolledText(log_frame, wrap=tk.WORD, state=tk.DISABLED)
        self.log_text.pack(fill=tk.BOTH, expand=True)

        # 购买卡密链接
        buy_carmi_label = tk.Label(self.root, text="点击获取卡密", fg="blue", cursor="hand2")
        buy_carmi_label.pack(side=tk.BOTTOM, fill=tk.X, padx=10, pady=5)
        buy_carmi_label.bind("<Button-1>", lambda e: self.open_buy_carmi_link())

    def load_config(self):
        """加载配置文件"""
        try:
            if os.path.exists(self.config_file):
                with open(self.config_file, 'r', encoding='utf-8') as f:
                    config = json.load(f)
                    self.carmi = config.get("carmi", "")
        except Exception as e:
            pass  # 如果加载失败，使用默认值

    def save_config(self):
        """保存配置文件"""
        try:
            config = {
                "carmi": self.carmi
            }
            with open(self.config_file, 'w', encoding='utf-8') as f:
                json.dump(config, f, ensure_ascii=False, indent=2)
        except Exception as e:
            pass  # 如果保存失败，忽略错误

    def verify_carmi(self):
        """验证卡密"""
        carmi = self.carmi_entry.get().strip()
        if not carmi:
            messagebox.showerror("错误", "请输入卡密")
            return False

        # 获取计算机唯一标识
        try:
            import uuid
            computer_uuid = str(uuid.getnode())
        except:
            # 如果无法获取硬件地址，则使用随机UUID
            import uuid as uuid_lib
            computer_uuid = str(uuid_lib.uuid4())

        # 准备请求数据
        data = {
            "goods_id": 3,  # Shopee工具的goods_id
            "carmi": carmi,
            "uuid": computer_uuid
        }

        try:
            # 发送POST请求验证卡密
            response = requests.post("https://shop.jypony.com/api/carmi-verify", json=data, timeout=10)

            if response.status_code == 200:
                result = response.json()
                if result.get("success"):
                    # 卡密验证成功
                    self.carmi = carmi
                    self.save_config()
                    # 显示卡密有效期
                    expired_at = result.get("data", {}).get("expired_at")
                    if expired_at is None:
                        self.expired_at_label.config(text="卡密有效期至: 永久有效")
                    elif expired_at:
                        self.expired_at_label.config(text=f"卡密有效期至: {expired_at}")
                    else:
                        self.expired_at_label.config(text="")
                    messagebox.showinfo("成功", result.get("message", "卡密验证成功"))
                    return True
                else:
                    # 卡密无效
                    self.expired_at_label.config(text="")
                    messagebox.showerror("验证失败", result.get("message", "卡密无效"))
                    return False
            else:
                self.expired_at_label.config(text="")
                messagebox.showerror("错误", f"验证服务异常: {response.status_code}")
                return False
        except Exception as e:
            self.expired_at_label.config(text="")
            messagebox.showerror("错误", f"验证请求失败: {str(e)}")
            return False

    def verify_carmi_auto(self):
        """自动验证卡密（用于定时检查）"""
        carmi = self.carmi_entry.get().strip()
        if not carmi:
            messagebox.showerror("错误", "请输入卡密")
            return False

        # 获取计算机唯一标识
        try:
            import uuid
            computer_uuid = str(uuid.getnode())
        except:
            # 如果无法获取硬件地址，则使用随机UUID
            import uuid as uuid_lib
            computer_uuid = str(uuid_lib.uuid4())

        # 准备请求数据
        data = {
            "goods_id": 2,  # Shopee工具的goods_id
            "carmi": carmi,
            "uuid": computer_uuid
        }

        try:
            # 发送POST请求验证卡密
            response = requests.post("https://shop.jypony.com/api/carmi-verify", json=data, timeout=10)

            if response.status_code == 200:
                result = response.json()
                if result.get("success"):
                    # 卡密验证成功
                    self.carmi = carmi
                    self.save_config()
                    # 显示卡密有效期
                    expired_at = result.get("data", {}).get("expired_at")
                    if expired_at is None:
                        self.expired_at_label.config(text="卡密有效期至: 永久有效")
                    elif expired_at:
                        self.expired_at_label.config(text=f"卡密有效期至: {expired_at}")
                    else:
                        self.expired_at_label.config(text="")
                    return True
                else:
                    # 卡密无效
                    self.expired_at_label.config(text="")
                    self.log("[LOG] 卡密验证失败: " + result.get("message", "卡密无效"))
                    return False
            else:
                self.expired_at_label.config(text="")
                self.log(f"[LOG] 卡密验证服务异常: {response.status_code}")
                return False
        except Exception as e:
            self.expired_at_label.config(text="")
            self.log(f"[LOG] 卡密验证请求失败: {str(e)}")
            return False

    def open_buy_carmi_link(self):
        """打开购买卡密链接"""
        import webbrowser
        webbrowser.open("https://shop.jypony.com/")

    def logout(self):
        cookies_path = self.base_path / "shopee" / "cookies.pkl"
        if cookies_path.exists():
            cookies_path.unlink()
            self.log("[LOG] 已删除cookies文件，退出登录成功")
        else:
            self.log("[LOG] 未找到cookies文件")

    def clear_log(self):
        """清除日志显示"""
        if self.log_text:
            self.log_text.config(state=tk.NORMAL)
            self.log_text.delete(1.0, tk.END)
            self.log_text.config(state=tk.DISABLED)
            
        # 删除日志文件
        if self.log_file and self.log_file.exists():
            try:
                self.log_file.unlink()
            except Exception as e:
                pass  # 忽略删除错误

    def on_shop_selected(self, event):
        """当选择店铺时触发"""
        selected_name = self.selected_shop.get()
        if not selected_name:
            # 禁用继续执行按钮
            self.continue_button.config(state=tk.DISABLED)
            return
            
        # 根据店铺名称找到对应的店铺ID
        for shop in self.shops:
            if shop.get("shop_name") == selected_name:
                shop_id = shop.get("shop_id")
                # 更新URL
                self.url.set(f"https://seller.shopee.cn/creator-center/insight/video?cnsc_shop_id={shop_id}")
                break
                
        # 标记店铺选择已完成，并启用继续执行按钮
        self.waiting_for_shop_selection = False
        self.shop_selection_completed = True
        self.continue_button.config(state=tk.NORMAL)

    def fetch_shops(self):
        """获取店铺信息"""
        # 在新线程中执行，避免阻塞GUI
        import threading
        self.execution_thread = threading.Thread(target=self.fetch_shops_execution)
        self.execution_thread.daemon = True
        self.execution_thread.start()
        
    def continue_execution(self):
        """继续执行上传流程"""
        # 如果已经获取到店铺信息但未选择店铺，则提示错误
        if hasattr(self, 'shop_info_loaded') and self.shop_info_loaded and not self.selected_shop.get():
            messagebox.showerror("错误", "请先选择一个店铺")
            return
            
        # 在新线程中执行，避免阻塞GUI
        import threading
        self.execution_thread = threading.Thread(target=self.execute)
        self.execution_thread.daemon = True
        self.execution_thread.start()
        
    def fetch_shops_execution(self):
        """获取店铺信息的执行逻辑"""
        global video_folder, image_folder, text_file, sku_file, cookies_file, driver, wait, app
        # 设置全局app变量，以便log函数可以访问GUI日志
        app = self

        try:
            # 首先验证卡密（如果超过1小时未验证）
            current_time = time.time()
            if current_time - self.last_verify_time > self.verify_interval:
                if not self.verify_carmi_auto():
                    self.log("[LOG] 卡密验证失败，无法执行任务")
                    return
                self.last_verify_time = current_time
            elif self.last_verify_time == 0:  # 第一次使用时也需验证
                if not self.verify_carmi_auto():
                    self.log("[LOG] 卡密验证失败，无法执行任务")
                    return
                self.last_verify_time = current_time

            # 更新路径（使用固定的脚本目录）
            base_dir = self.base_path
            video_folder = base_dir / "shopee" / "videos"
            image_folder = base_dir / "shopee" / "images"
            text_file = base_dir / "shopee" / "file.txt"
            sku_file = base_dir / "shopee" / "sku.txt"
            cookies_file = base_dir / "shopee" / "cookies.pkl"

            # 确保目录存在
            (base_dir / "shopee").mkdir(parents=True, exist_ok=True)
            video_folder.mkdir(parents=True, exist_ok=True)
            image_folder.mkdir(parents=True, exist_ok=True)

            # 初始化浏览器（仅在尚未初始化时）
            if 'driver' not in globals() or driver is None:
                self.log("[LOG] 正在初始化浏览器...")

                # 配置Chrome选项
                chrome_options = Options()
                chrome_options.add_argument("--no-sandbox")
                chrome_options.add_argument("--disable-dev-shm-usage")

                # 自动管理ChromeDriver版本，使用缓存避免重复下载
                from webdriver_manager.core.driver_cache import DriverCacheManager
                try:
                    cache_manager = DriverCacheManager()
                    service = Service(ChromeDriverManager(cache_manager=cache_manager).install())
                except Exception as e:
                    self.log(f"[ERROR] WebDriver Manager初始化失败: {e}")
                    self.log("[LOG] 尝试不使用缓存管理器...")
                    service = Service(ChromeDriverManager().install())
                    
                driver = webdriver.Chrome(service=service, options=chrome_options)
                wait = WebDriverWait(driver, 60)
            else:
                self.log("[LOG] 使用已存在的浏览器实例")

            # 获取任务间隔时间
            interval_time = self.interval.get()
            self.log(f"[LOG] 任务间隔时间设置为: {interval_time}秒")

            # 执行获取店铺流程
            login_or_load_session(self.url.get())
            
        except Exception as e:
            self.log(f"[ERROR] 获取店铺信息过程中发生错误: {str(e)}")

    def stop_execution_process(self):
        """停止执行过程"""
        self.stop_execution = True
        self.log("[LOG] 正在停止执行...")
        if 'driver' in globals() and driver:
            try:
                driver.quit()
                self.log("[LOG] 浏览器已关闭")
            except Exception as e:
                self.log(f"[ERROR] 关闭浏览器时发生错误: {str(e)}")

    def execute(self):
        global video_folder, image_folder, text_file, sku_file, cookies_file, driver, wait, app
        # 设置全局app变量，以便log函数可以访问GUI日志
        app = self

        # 检查是否已经执行过完整流程
        # 只有在完成整个上传流程后才限制重复执行
        if hasattr(self, 'full_execution_completed') and self.full_execution_completed:
            self.log("[LOG] 执行已经进行中或已完成，请重新启动程序以再次执行")
            return
            
        # 设置执行标志（除非是在等待店铺选择）
        if not (hasattr(self, 'waiting_for_shop_selection') and self.waiting_for_shop_selection):
            self.execution_started = True
        else:
            # 如果正在等待店铺选择，重置执行状态以允许继续执行
            self.execution_started = False

        try:
            # 首先验证卡密（如果超过1小时未验证）
            current_time = time.time()
            if current_time - self.last_verify_time > self.verify_interval:
                if not self.verify_carmi_auto():
                    self.log("[LOG] 卡密验证失败，无法执行任务")
                    return
                self.last_verify_time = current_time
            elif self.last_verify_time == 0:  # 第一次使用时也需验证
                if not self.verify_carmi_auto():
                    self.log("[LOG] 卡密验证失败，无法执行任务")
                    return
                self.last_verify_time = current_time

            # 更新路径（使用固定的脚本目录）
            base_dir = self.base_path
            video_folder = base_dir / "shopee" / "videos"
            image_folder = base_dir / "shopee" / "images"
            text_file = base_dir / "shopee" / "file.txt"
            sku_file = base_dir / "shopee" / "sku.txt"
            cookies_file = base_dir / "shopee" / "cookies.pkl"

            # 确保目录存在
            (base_dir / "shopee").mkdir(parents=True, exist_ok=True)
            video_folder.mkdir(parents=True, exist_ok=True)
            image_folder.mkdir(parents=True, exist_ok=True)

            # 初始化浏览器（仅在尚未初始化时）
            if 'driver' not in globals() or driver is None:
                self.log("[LOG] 正在初始化浏览器...")

                # 配置Chrome选项
                chrome_options = Options()
                chrome_options.add_argument("--no-sandbox")
                chrome_options.add_argument("--disable-dev-shm-usage")

                # 自动管理ChromeDriver版本，使用缓存避免重复下载
                from webdriver_manager.core.driver_cache import DriverCacheManager
                try:
                    cache_manager = DriverCacheManager()
                    service = Service(ChromeDriverManager(cache_manager=cache_manager).install())
                except Exception as e:
                    self.log(f"[ERROR] WebDriver Manager初始化失败: {e}")
                    self.log("[LOG] 尝试不使用缓存管理器...")
                    service = Service(ChromeDriverManager().install())
                    
                driver = webdriver.Chrome(service=service, options=chrome_options)
                wait = WebDriverWait(driver, 60)
            else:
                self.log("[LOG] 使用已存在的浏览器实例")

            # 获取任务间隔时间
            interval_time = self.interval.get()
            self.log(f"[LOG] 任务间隔时间设置为: {interval_time}秒")

            # 执行上传流程
            if not self.stop_execution:
                login_or_load_session(self.url.get())
                
            # 如果已获取店铺信息但未选择店铺，则暂停执行并提示用户选择
            if hasattr(self, 'shop_info_loaded') and self.shop_info_loaded:
                # 如果是首次执行或正在等待店铺选择
                if not self.selected_shop.get() or (hasattr(self, 'waiting_for_shop_selection') and self.waiting_for_shop_selection):
                    self.log("[LOG] 已获取店铺信息，请选择一个店铺后再继续执行")
                    # 这里我们不继续执行后续步骤，等待用户选择店铺后点击"继续执行"
                    return
                else:
                    # 标记店铺选择已完成
                    self.shop_selection_completed = True
                    
            # 如果店铺选择已完成，继续执行后续步骤
            if hasattr(self, 'shop_selection_completed') and self.shop_selection_completed:
                if not self.stop_execution:
                    navigate_to_upload_page()
                if not self.stop_execution:
                    click_upload_button()
                if not self.stop_execution:
                    process_videos()

                if self.stop_execution:
                    self.log("[LOG] 执行已停止")
                else:
                    self.log("[LOG] 执行完成")
                    # 标记整个流程已完成
                    self.full_execution_completed = True

        except Exception as e:
            self.log(f"[ERROR] 执行过程中发生错误: {str(e)}")
        finally:
            self.stop_execution = False
            # 不再关闭浏览器
            # if 'driver' in globals():
            #     try:
            #         driver.quit()
            #     except Exception as e:
            #         self.log(f"[ERROR] 关闭浏览器时发生错误: {str(e)}")
            # 清除全局app引用
            # app = None

            #     except Exception as e:
            #         self.log(f"[ERROR] 关闭浏览器时发生错误: {str(e)}")
            # 清除全局app引用
            # app = None

    def log(self, message):
        if self.log_text:
            # 启用文本框以插入内容
            self.log_text.config(state=tk.NORMAL)
            self.log_text.insert(tk.END, message + "\n")
            self.log_text.see(tk.END)
            self.log_text.config(state=tk.DISABLED)  # 禁用编辑
            self.root.update_idletasks()

            # 同时写入日志文件
            if self.log_file:
                try:
                    with open(self.log_file, "a", encoding="utf-8") as f:
                        f.write(message + "\n")
                except Exception as e:
                    pass  # 忽略日志写入错误

    def show_instructions(self):
        """显示使用说明"""
        instructions = """
使用说明：

1. 点击"获取店铺"按钮，执行登录流程并获取店铺列表信息，填充到下拉选择框中

2. 从下拉框中选择要操作的店铺

3. 点击"继续执行"按钮，将选中的店铺ID拼接到目标URL中，跳转到对应的店铺视频上传页面，并启动视频上传流程

4. 文件目录规范
   start 执行文件
   shopee 资源文件夹
   |--videos 按时间正序视频
   |--file.txt 逐行储存视频说明
   |--sku.txt 逐行储存skuId, 多skuId情况下用英文逗号隔开
   |--cookies.pkl 登录信息凭证，"退出登录"即是删除该凭证
   |--execution.log 系统执行日志

5. 特别注意：运行一遍之后，要执行其他任务的话，得关闭程序重新打开才可以
        """
        
        # 创建一个新的窗口显示说明
        instruction_window = tk.Toplevel(self.root)
        instruction_window.title("使用说明")
        instruction_window.geometry("600x400")
        
        # 创建文本框显示说明内容
        text_widget = tk.Text(instruction_window, wrap=tk.WORD)
        text_widget.insert(tk.END, instructions)
        text_widget.config(state=tk.DISABLED)  # 设置为只读
        
        # 添加滚动条
        scrollbar = tk.Scrollbar(instruction_window, orient=tk.VERTICAL, command=text_widget.yview)
        text_widget.config(yscrollcommand=scrollbar.set)
        
        # 布局
        text_widget.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=10, pady=10)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y, pady=10)

# 创建全局的日志函数
def log(message):
    # 如果在GUI模式下运行，使用GUI的日志函数
    if 'app' in globals() and app and hasattr(app, 'log'):
        app.log(message)
    # 同时打印到控制台
    print(message)

def get_video_duration(video_path):
    """获取视频时长（秒）"""
    try:
        # 使用ffprobe获取视频信息
        cmd = [
            'ffprobe', 
            '-v', 'quiet', 
            '-print_format', 'json', 
            '-show_format', 
            '-show_streams', 
            video_path
        ]
        
        result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
        if result.returncode == 0:
            data = json.loads(result.stdout)
            # 尝试从streams获取duration
            if 'streams' in data and len(data['streams']) > 0:
                for stream in data['streams']:
                    if 'duration' in stream:
                        return float(stream['duration'])
            # 如果streams中没有duration，尝试从format获取
            if 'format' in data and 'duration' in data['format']:
                return float(data['format']['duration'])
        return 0
    except Exception as e:
        log(f"[LOG] 获取视频时长出错: {e}")
        return 0

def gui_main():
    if not GUI_AVAILABLE:
        print("GUI不可用，请安装tkinter库")
        return
        
    root = tk.Tk()
    app = ShopeeUploaderGUI(root)
    root.mainloop()

if __name__ == "__main__":
    if len(sys.argv) > 1 and sys.argv[1] == "--help":
        show_help()
    elif len(sys.argv) > 1 and sys.argv[1] == "--cmd":
        gui_main()
    else:
        # 默认启动时调用 gui_main()
        gui_main()